﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2013  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GLES_FRAMEBUFFER_H
#define _UXS_GLES_FRAMEBUFFER_H

#include "GLESStateManager.h"
#include "GLESObjectManager.h"
#include "GLESTexture.h"
#include "GLESRenderBuffer.h"

namespace UX_STL
{
namespace GLESshell
{
	
	//
	// Frame Buffer Object
	//

	class _EXPORT_ GLESFrameBuffer : public GLESBaseObject
	{
	private:
		enum {
			_DEPTH			= 0,
			_STENCIL,
			_COLOR,
			_BUFFER_SIZE	= _COLOR + 1,
		};

		struct TBuffer
		{
			GLuint					uID;
			gl_texture::type		eTarget;
			gl_fbo_attach::type		eAttach;
			///
			TBuffer(GLuint id = 0, gl_texture::type target = gl_texture::_UNKNOWN,
					gl_fbo_attach::type attach = gl_fbo_attach::NONE):
					uID(id), eTarget(target), eAttach(attach) {}
		};

		typedef static_array< TBuffer, _BUFFER_SIZE >				buffers_t;

	private:
		buffers_t		_aBuffers;			// Depth + Stencil + Colors buffers
		GLuint			_uID;

	private:
		static uint					_s_AttachmentToIndex(gl_fbo_attach::type eAttach);
		static gl_fbo_attach::type	_s_IndexToAttachment(uint idx);

	public:
		GLESFrameBuffer();
		GLESFrameBuffer(GLESFrameBuffer &);
		~GLESFrameBuffer();

		bool  Create();
		void  Destroy();
		bool  CheckStatus();
		
		void  Bind()	 const;
		void  Unbind()	 const;
		bool  IsBinded() const;
		
		void  Attach(const GLESTexture *pTex, gl_fbo_attach::type eAttach, uint uLevel = 0);	// attach all layers and faces of texture
		void  Attach2D(const GLESTexture2D *pTex, gl_fbo_attach::type eAttach, uint uLevel = 0);
		void  Attach3D(const GLESTexture3D *pTex, gl_fbo_attach::type eAttach, uint uDepth = 0, uint uLevel = 0);
		void  AttachFace(const GLESTextureCube *pTex, gl_fbo_attach::type eAttach, gl_cube_face::type eFace, uint uLevel = 0);
		void  Attach(const GLESRenderBuffer *pRB, gl_fbo_attach::type eAttach);	// render buffer

		void  Detach(gl_fbo_attach::type eAttach);
		void  DetachAll();

		void  ClearAll() const;

		GLuint	Id()						const	{ return _uID; }
		GLuint	GetDepthBuffer()			const	{ return _aBuffers[_DEPTH].uID; }
		GLuint	GetStencilBuffer()			const	{ return _aBuffers[_STENCIL].uID; }
		GLuint	GetColorBuffer()			const	{ return _aBuffers[_COLOR].uID; }
		GLenum	Target()					const	{ return GL_FRAMEBUFFER; }
	};



	//
	// Default Frame Buffer
	//

	class _EXPORT_ GLESDefaultFrameBuffer : public GLESBaseObject
	{
	public:
		static bool Read(const ivec2 &vOffset, const uvec2 &vDim, gl_pixel_format::type eFormat, gl_pixel::type eType, void *pData);
		static bool GetImageDataSize(const uvec2 &vDim, gl_pixel_format::type eFormat, gl_pixel::type eType, usize &uDataSize);
		
		static void ClearBuffers(bool bColor = true, bool bDepth = false, bool bStencil = false);
		static void SetClearColor(const color4f &vColor);
		static void SetClearDepth(float fDepth);
		static void SetClearStencil(int iStencil);
	};

	

	
	
//------------------------- GLESFrameBuffer -----------------------//
/*
=================================================
	устанавливает кадровый буфер текущим
=================================================
*/
	inline void GLESFrameBuffer::Bind() const
	{
		SMAN()->BindFramebuffer( _uID );
	}

/*
=================================================
	возвращает стандартный буфер
=================================================
*/
	inline void GLESFrameBuffer::Unbind() const
	{
		ASSERT( IsBinded() );
		SMAN()->BindFramebuffer( 0 );
	}
	
/*
=================================================
	явлеется ли этот кдровый буфер текущим
=================================================
*/
	inline bool GLESFrameBuffer::IsBinded() const
	{
		return SMAN()->GetFBO() == _uID;
	}

/*
=================================================
	очищает все активные присоединенные цели рендера
=================================================
*/
	inline void GLESFrameBuffer::ClearAll() const
	{
		ASSERT( IsBinded() );
		GLESDefaultFrameBuffer::ClearBuffers(	_aBuffers[_COLOR].uID != 0,
												_aBuffers[_DEPTH].uID != 0,
												_aBuffers[_STENCIL].uID != 0 );
	}

/*
=================================================
	возвращает индекс в буфере целей рендеринга
=================================================
*/
	inline uint GLESFrameBuffer::_s_AttachmentToIndex(gl_fbo_attach::type eAttach)
	{
		switch ( eAttach )
		{
			case gl_fbo_attach::COLOR_0 :	return _COLOR;
			case gl_fbo_attach::DEPTH :		return _DEPTH;
			case gl_fbo_attach::STENCIL :	return _STENCIL;
		}
		WARNING( "unsupported FBO attachment type" );
		return -1;
	}

/*
=================================================
	возвращает константу по которой присоединяется
	цель рендеринга
=================================================
*/
	inline gl_fbo_attach::type GLESFrameBuffer::_s_IndexToAttachment(uint idx)
	{
		switch ( idx )
		{
			case _COLOR	:	return gl_fbo_attach::COLOR_0;
			case _DEPTH :	return gl_fbo_attach::DEPTH;
			case _STENCIL :	return gl_fbo_attach::STENCIL;
		};

		WARNING( "Unknwon attachment index" );
		return (gl_fbo_attach::type)-1;
	}

/*
=================================================
	присоединяет 2Д текстуру
=================================================
*/
	inline void GLESFrameBuffer::Attach2D(const GLESTexture2D *pTex, gl_fbo_attach::type eAttach, uint uLevel)
	{
		ASSERT( IsBinded() );

		const uint	idx			= _s_AttachmentToIndex( eAttach );
		_aBuffers[idx].uID		= pTex->Id();
		_aBuffers[idx].eTarget	= pTex->Target();
		GL_CALL( glFramebufferTexture2D( Target(), eAttach, pTex->Target(), pTex->Id(), uLevel ) );
	}
	
/*
=================================================
	присоединяет 3Д текстуру
=================================================
*/
	inline void GLESFrameBuffer::Attach3D(const GLESTexture3D *pTex, gl_fbo_attach::type eAttach, uint uDepth, uint uLevel)
	{
		ASSERT( IsBinded() );

		const uint	idx			= _s_AttachmentToIndex( eAttach );
		_aBuffers[idx].uID		= pTex->Id();
		_aBuffers[idx].eTarget	= pTex->Target();
		GL_CALL( OES_texture_3D::glFramebufferTexture3D( Target(), eAttach, pTex->Target(), pTex->Id(), uLevel, uDepth ) );
	}

/*
=================================================
	присоединяет грань кубической текстуры
=================================================
*/
	inline void GLESFrameBuffer::AttachFace(const GLESTextureCube *pTex, gl_fbo_attach::type eAttach, gl_cube_face::type eFace, uint uLevel)
	{
		ASSERT( IsBinded() );
		ASSERT( eFace >= gl_cube_face::_FIRST && eFace <= gl_cube_face::_LAST );
		
		const uint	idx			= _s_AttachmentToIndex( eAttach );
		_aBuffers[idx].uID		= pTex->Id();
		_aBuffers[idx].eTarget	= (gl_texture::type)eFace;
		GL_CALL( glFramebufferTexture2D( Target(), eAttach, eFace, pTex->Id(), uLevel ) );
	}
	
/*
=================================================
	присоединяет рендер буфер
=================================================
*/
	inline void GLESFrameBuffer::Attach(const GLESRenderBuffer *pRB, gl_fbo_attach::type eAttach)
	{
		ASSERT( IsBinded() );

		const uint	idx			= _s_AttachmentToIndex( eAttach );
		_aBuffers[idx].uID		= pRB->Id();
		_aBuffers[idx].eTarget	= (gl_texture::type)pRB->Target();
		GL_CALL( glFramebufferRenderbuffer( Target(), eAttach, pRB->Target(), pRB->Id() ) );
	}
	
/*
=================================================
	удаляет текстуру из ФБО
=================================================
*/
	inline void GLESFrameBuffer::Detach(gl_fbo_attach::type eAttach)
	{
		ASSERT( IsBinded() );

		const uint	idx			= _s_AttachmentToIndex( eAttach );
		_aBuffers[idx].uID		= 0;
		_aBuffers[idx].eTarget	= gl_texture::_UNKNOWN;
		GL_CALL( glFramebufferTexture2D( Target(), eAttach, gl_texture::TEXTURE_2D, 0, 0 ) );
	}



	
//---------------------- GLESDefaultFrameBuffer -------------------//
/*
=================================================
	считывает пиксели из текущего read_buffer'а
=================================================
*/
	inline bool GLESDefaultFrameBuffer::Read(const ivec2 &vOffset, const uvec2 &vDim, gl_pixel_format::type eFormat, gl_pixel::type eType, void *pData)
	{
		GL_CALL_R( glReadPixels( vOffset.x, vOffset.y, vDim.x, vDim.y, eFormat, eType, pData ) );
		return true;
	}

/*
=================================================
	очищает все буферы
=================================================
*/
	inline void GLESDefaultFrameBuffer::ClearBuffers(bool bColor, bool bDepth, bool bStencil)
	{
		GL_CALL( glClear( (int(bColor) << 14) | (int(bDepth) << 8) | (int(bStencil) << 10) ) );
	}
	
/*
=================================================
	устанавливает значения для очисти буферов
=================================================
*/
	inline void GLESDefaultFrameBuffer::SetClearColor(const color4f &vColor)
	{
		GL_CALL( glClearColor( vColor.x, vColor.y, vColor.z, vColor.w ) );
	}

	inline void GLESDefaultFrameBuffer::SetClearDepth(float fDepth)
	{
		GL_CALL( glClearDepthf( fDepth ) );
	}

	inline void GLESDefaultFrameBuffer::SetClearStencil(int iStencil)
	{
		GL_CALL( glClearStencil( iStencil ) );
	}

//-------------------------------------------------------------------

}	// GLESshell
}	// UX_STL

#endif	// _UXS_GLES_FRAMEBUFFER_H
